
#include "canvas_skia.h"

#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"

#include "skia/ext/bitmap_platform_device_win.h"
#include "skia/ext/skia_utils_win.h"

#include "color_utils.h"
#include "font.h"
#include "rect.h"

namespace
{

    static inline int Round(double x)
    {
        // Why oh why is this not in a standard header?
        return static_cast<int>(floor(x+0.5));
    }

    // We make sure that LTR text we draw in an RTL context is modified
    // appropriately to make sure it maintains it LTR orientation.
    void DoDrawText(HDC hdc, const string16& text, RECT* text_bounds, int flags)
    {
        // Only adjust string directionality if both of the following are true:
        // 1. The current locale is RTL.
        // 2. The string itself has RTL directionality.
        const wchar_t* string_ptr = text.c_str();
        int string_size = static_cast<int>(text.length());
        
        string16 localized_text;
        if(flags & DT_RTLREADING)
        {
            localized_text = text;
            base::i18n::AdjustStringForLocaleDirection(&localized_text);
            string_ptr = localized_text.c_str();
            string_size = static_cast<int>(localized_text.length());
        }

        DrawText(hdc, string_ptr, string_size, text_bounds, flags);
    }

    // Compute the windows flags necessary to implement the provided text Canvas
    // flags.
    int ComputeFormatFlags(int flags, const string16& text)
    {
        // Setting the text alignment explicitly in case it hasn't already been set.
        // This will make sure that we don't align text to the left on RTL locales
        // just because no alignment flag was passed to DrawStringInt().
        if(!(flags & (gfx::Canvas::TEXT_ALIGN_CENTER|
            gfx::Canvas::TEXT_ALIGN_RIGHT|gfx::Canvas::TEXT_ALIGN_LEFT)))
        {
            flags |= gfx::CanvasSkia::DefaultCanvasTextAlignment();
        }

        // horizontal alignment
        int f = 0;
        if(flags & gfx::Canvas::TEXT_ALIGN_CENTER)
        {
            f |= DT_CENTER;
        }
        else if(flags & gfx::Canvas::TEXT_ALIGN_RIGHT)
        {
            f |= DT_RIGHT;
        }
        else
        {
            f |= DT_LEFT;
        }

        // vertical alignment
        if(flags & gfx::Canvas::TEXT_VALIGN_TOP)
        {
            f |= DT_TOP;
        }
        else if(flags & gfx::Canvas::TEXT_VALIGN_BOTTOM)
        {
            f |= DT_BOTTOM;
        }
        else
        {
            f |= DT_VCENTER;
        }

        if(flags & gfx::Canvas::MULTI_LINE)
        {
            f |= DT_WORDBREAK;
            if(flags & gfx::Canvas::CHARACTER_BREAK)
            {
                f |= DT_EDITCONTROL;  // Turns on character breaking (not documented)
            }
            else if(!(flags & gfx::Canvas::NO_ELLIPSIS))
            {
                f |= DT_WORD_ELLIPSIS;
            }
        }
        else
        {
            f |= DT_SINGLELINE;
        }

        if(flags & gfx::Canvas::HIDE_PREFIX)
        {
            f |= DT_HIDEPREFIX;
        }
        else if((flags & gfx::Canvas::SHOW_PREFIX) == 0)
        {
            f |= DT_NOPREFIX;
        }

        if(!(flags & gfx::Canvas::NO_ELLIPSIS))
        {
            f |= DT_END_ELLIPSIS;
        }

        // In order to make sure RTL/BiDi strings are rendered correctly, we must
        // pass the flag DT_RTLREADING to DrawText (when the locale's language is
        // a right-to-left language) so that Windows does the right thing.
        //
        // In addition to correctly displaying text containing both RTL and LTR
        // elements (for example, a string containing a telephone number within a
        // sentence in Hebrew, or a sentence in Hebrew that contains a word in
        // English) this flag also makes sure that if there is not enough space to
        // display the entire string, the ellipsis is displayed on the left hand side
        // of the truncated string and not on the right hand side.
        //
        // We make a distinction between Chrome UI strings and text coming from a web
        // page.
        //
        // For text coming from a web page we determine the alignment based on the
        // first character with strong directionality. If the directionality of the
        // first character with strong directionality in the text is LTR, the
        // alignment is set to DT_LEFT, and the directionality should not be set as
        // DT_RTLREADING. If the directionality of the first character with strong
        // directionality in the text is RTL, its alignment is set to DT_RIGHT, and
        // its directionality is set as DT_RTLREADING through
        // FORCE_RTL_DIRECTIONALITY.
        //
        // This heuristic doesn't work for Chrome UI strings since even in RTL
        // locales, some of those might start with English text but we know they're
        // localized so their directionality should be set as DT_RTLREADING if it
        // contains strong RTL characters.
        //
        // Caveat: If the string is purely LTR, don't set DTL_RTLREADING since when
        // the flag is set, LRE-PDF don't have the desired effect of rendering
        // multiline English-only text as LTR.
        //
        // Note that if the caller is explicitly requesting displaying the text
        // using RTL directionality then we respect that and pass DT_RTLREADING to
        // ::DrawText even if the locale is LTR.
        int force_rtl = (flags & gfx::Canvas::FORCE_RTL_DIRECTIONALITY);
        int force_ltr = (flags & gfx::Canvas::FORCE_LTR_DIRECTIONALITY);
        bool is_rtl = base::i18n::IsRTL();
        bool string_contains_strong_rtl_chars =
            base::i18n::StringContainsStrongRTLChars(text);
        if(force_rtl || (!force_ltr && is_rtl && string_contains_strong_rtl_chars))
        {
            f |= DT_RTLREADING;
        }

        return f;
    }

    // Changes the alpha of the given bitmap.
    // If |fade_to_right| is true then the rect fades from opaque to clear,
    // otherwise the rect fades from clear to opaque.
    void FadeBitmapRect(SkDevice& bmp_device,
        const gfx::Rect& rect, bool fade_to_right)
    {
        SkBitmap bmp = bmp_device.accessBitmap(true);
        DCHECK_EQ(SkBitmap::kARGB_8888_Config, bmp.config());
        SkAutoLockPixels lock(bmp);
        float total_width = static_cast<float>(rect.width());

        for(int x=rect.x(); x<rect.right(); ++x)
        {
            float cur_width = static_cast<float>(fade_to_right ?
                rect.right()-x :  x-rect.x());
            // We want the fade effect to go from 0.2 to 1.0.
            float alpha_percent = ((cur_width / total_width) * 0.8f) + 0.2f;

            for(int y=rect.y(); y<rect.bottom(); ++y)
            {
                SkColor color = bmp.getColor(x, y);
                SkAlpha alpha = static_cast<SkAlpha>(SkColorGetA(color) * alpha_percent);
                *bmp.getAddr32(x, y) = SkPreMultiplyColor(SkColorSetA(color, alpha));
            }
        }
    }

    // DrawText() doesn't support alpha channels. To create a transparent background
    // this function draws black on white. It then uses the intensity of black
    // to determine how much alpha to use. The text is drawn in |gfx_text_rect| and
    // clipped to |gfx_draw_rect|.
    void DrawTextAndClearBackground(SkCanvas* bmp_canvas,
        HFONT font,
        COLORREF text_color,
        const string16& text,
        int flags,
        const gfx::Rect& gfx_text_rect,
        const gfx::Rect& gfx_draw_rect)
    {
        skia::ScopedPlatformPaint scoped_platform_paint(bmp_canvas);
        HDC hdc = scoped_platform_paint.GetPlatformSurface();

        // Clear the background by filling with white.
        HBRUSH fill_brush = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
        HANDLE old_brush = SelectObject(hdc, fill_brush);
        RECT draw_rect = gfx_draw_rect.ToRECT();
        FillRect(hdc, &draw_rect, fill_brush);
        SelectObject(hdc, old_brush);

        // Set black text with trasparent background.
        SetBkMode(hdc, TRANSPARENT);
        SetTextColor(hdc, 0);

        // Draw the text.
        int save_dc_id = SaveDC(hdc);
        // Clip the text to the draw destination.
        IntersectClipRect(hdc, draw_rect.left, draw_rect.top,
            draw_rect.right, draw_rect.bottom);
        SelectObject(hdc, font);
        RECT text_rect = gfx_text_rect.ToRECT();
        DoDrawText(hdc, text, &text_rect,
            ComputeFormatFlags(flags, text));
        RestoreDC(hdc, save_dc_id);

        BYTE text_color_r = GetRValue(text_color);
        BYTE text_color_g = GetGValue(text_color);
        BYTE text_color_b = GetBValue(text_color);

        SkBitmap bmp = bmp_canvas->getTopDevice()->accessBitmap(true);
        DCHECK_EQ(SkBitmap::kARGB_8888_Config, bmp.config());
        SkAutoLockPixels lock(bmp);

        // At this point the bitmap has black text on white.
        // The intensity of black tells us the alpha value of the text.
        for(int y=draw_rect.top; y<draw_rect.bottom; ++y)
        {
            for(int x=draw_rect.left; x<draw_rect.right; ++x)
            {
                // Gets the color directly. DrawText doesn't premultiply alpha so
                // using SkBitmap::getColor() won't work here.
                SkColor color = *bmp.getAddr32(x, y);
                // Calculate the alpha using the luminance. Since this is black text
                // on a white background the luminosity must be inverted.
                BYTE alpha = 0xFF - gfx::GetLuminanceForColor(color);
                *bmp.getAddr32(x, y) = SkPreMultiplyColor(SkColorSetARGB(alpha,
                    text_color_r, text_color_g, text_color_b));
            }
        }
    }

    // Draws the given text with a fade out gradient. |bmp_device| is a bitmap
    // that is used to temporary drawing. The text is drawn in |text_rect| and
    // clipped to |draw_rect|.
    void DrawTextGradientPart(HDC hdc,
        SkCanvas* bmp_canvas,
        const string16& text,
        const SkColor& color,
        HFONT font,
        const gfx::Rect& text_rect,
        const gfx::Rect& draw_rect,
        bool fade_to_right,
        int flags)
    {
        DrawTextAndClearBackground(bmp_canvas, font, skia::SkColorToCOLORREF(color),
            text, flags, text_rect, draw_rect);
        FadeBitmapRect(*bmp_canvas->getTopDevice(), draw_rect, fade_to_right);
        BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };

        skia::ScopedPlatformPaint scoped_platform_paint(bmp_canvas);
        HDC bmp_hdc = scoped_platform_paint.GetPlatformSurface();
        AlphaBlend(hdc, draw_rect.x(), draw_rect.y(), draw_rect.width(),
            draw_rect.height(), bmp_hdc, draw_rect.x(), draw_rect.y(),
            draw_rect.width(), draw_rect.height(), blend);
    }

    enum PrimarySide
    {
        PrimaryOnLeft,
        PrimaryOnRight,
    };

    // Divides |rect| horizontally into a |primary| of width |primary_width| and a
    // |secondary| taking up the remainder.
    void DivideRect(const gfx::Rect& rect,
        PrimarySide primary_side,
        int primary_width,
        gfx::Rect* primary,
        gfx::Rect* secondary)
    {
        *primary = rect;
        *secondary = rect;
        int remainder = rect.width() - primary_width;

        switch(primary_side)
        {
        case PrimaryOnLeft:
            primary->Inset(0, 0, remainder, 0);
            secondary->Inset(primary_width, 0, 0, 0);
            break;
        case PrimaryOnRight:
            primary->Inset(remainder, 0, 0, 0);
            secondary->Inset(0, 0, primary_width, 0);
            break;
        }
    }

}

namespace gfx
{

    CanvasSkia::CanvasSkia(int width, int height, bool is_opaque)
        : skia::PlatformCanvas(width, height, is_opaque) {}

    CanvasSkia::CanvasSkia() : skia::PlatformCanvas() {}

    CanvasSkia::~CanvasSkia() {}

    // static
    void CanvasSkia::SizeStringInt(const string16& text,
        const Font& font,
        int* width, int* height,
        int flags)
    {
        // Clamp the max amount of text we'll measure to 2K.  When the string is
        // actually drawn, it will be clipped to whatever size box is provided, and
        // the time to do that doesn't depend on the length being clipped off.
        const int kMaxStringLength = 2048 - 1; // So the trailing \0 fits in 2K.
        string16 clamped_string(text.substr(0, kMaxStringLength));

        if(*width == 0)
        {
            // If multi-line + character break are on, the computed width will be one
            // character wide (useless).  Furthermore, if in this case the provided text
            // contains very long "words" (substrings without a word-breaking point),
            // DrawText() can run extremely slowly (e.g. several seconds).  So in this
            // case, we turn character breaking off to get a more accurate "desired"
            // width and avoid the slowdown.
            if(flags & (Canvas::MULTI_LINE|Canvas::CHARACTER_BREAK))
            {
                flags &= ~Canvas::CHARACTER_BREAK;
            }

            // Weird undocumented behavior: if the width is 0, DoDrawText() won't
            // calculate a size at all.  So set it to 1, which it will then change.
            if(!text.empty())
            {
                *width = 1;
            }
        }
        RECT r = { 0, 0, *width, *height };

        HDC dc = GetDC(NULL);
        HFONT old_font = static_cast<HFONT>(SelectObject(dc, font.GetNativeFont()));
        DoDrawText(dc, clamped_string, &r,
            ComputeFormatFlags(flags, clamped_string)|DT_CALCRECT);
        SelectObject(dc, old_font);
        ReleaseDC(NULL, dc);

        *width = r.right;
        *height = r.bottom;
    }

    void CanvasSkia::DrawStringInt(const string16& text,
        HFONT font,
        const SkColor& color,
        int x, int y, int w, int h,
        int flags)
    {
        SkRect fclip;
        if(!getClipBounds(&fclip))
        {
            return;
        }
        RECT text_bounds = { x, y, x+w, y+h };
        SkIRect clip;
        fclip.round(&clip);
        if(!clip.intersect(skia::RECTToSkIRect(text_bounds)))
        {
            return;
        }

        // Clamp the max amount of text we'll draw to 32K.  There seem to be bugs in
        // DrawText() if you e.g. ask it to character-break a no-whitespace string of
        // length > 43680 (for which it draws nothing), and since we clamped to 2K in
        // SizeStringInt() we're unlikely to be able to display this much anyway.
        const int kMaxStringLength = 32768 - 1; // So the trailing \0 fits in 32K.
        string16 clamped_string(text.substr(0, kMaxStringLength));

        HDC dc;
        HFONT old_font;
        {
            skia::ScopedPlatformPaint scoped_platform_paint(this);
            dc = scoped_platform_paint.GetPlatformSurface();
            SetBkMode(dc, TRANSPARENT);
            old_font = (HFONT)SelectObject(dc, font);
            COLORREF brush_color = RGB(SkColorGetR(color), SkColorGetG(color),
                SkColorGetB(color));
            SetTextColor(dc, brush_color);

            int f = ComputeFormatFlags(flags, clamped_string);
            DoDrawText(dc, clamped_string, &text_bounds, f);
        }

        // Restore the old font. This way we don't have to worry if the caller
        // deletes the font and the DC lives longer.
        SelectObject(dc, old_font);

        // Windows will have cleared the alpha channel of the text we drew. Assume
        // we're drawing to an opaque surface, or at least the text rect area is
        // opaque.
        skia::MakeOpaque(this, clip.fLeft, clip.fTop, clip.width(), clip.height());
    }

    void CanvasSkia::DrawStringInt(const string16& text,
        const Font& font,
        const SkColor& color,
        int x, int y, int w, int h,
        int flags)
    {
        DrawStringInt(text, font.GetNativeFont(), color, x, y, w, h, flags);
    }

    // Checks each pixel immediately adjacent to the given pixel in the bitmap. If
    // any of them are not the halo color, returns true. This defines the halo of
    // pixels that will appear around the text. Note that we have to check each
    // pixel against both the halo color and transparent since DrawStringWithHalo
    // will modify the bitmap as it goes, and clears pixels shouldn't count as
    // changed.
    static bool pixelShouldGetHalo(const SkBitmap& bitmap,
        int x, int y, SkColor halo_color)
    {
        if(x>0 &&
            *bitmap.getAddr32(x-1, y)!=halo_color &&
            *bitmap.getAddr32(x-1, y)!=0)
        {
            return true; // Touched pixel to the left.
        }
        if(x<bitmap.width()-1 &&
            *bitmap.getAddr32(x+1, y)!=halo_color &&
            *bitmap.getAddr32(x+1, y)!=0)
        {
            return true; // Touched pixel to the right.
        }
        if(y>0 &&
            *bitmap.getAddr32(x, y-1)!=halo_color &&
            *bitmap.getAddr32(x, y-1)!=0)
        {
            return true; // Touched pixel above.
        }
        if(y<bitmap.height()-1 &&
            *bitmap.getAddr32(x, y+1)!=halo_color &&
            *bitmap.getAddr32(x, y+1)!=0)
        {
            return true; // Touched pixel below.
        }
        return false;
    }

    void CanvasSkia::DrawStringWithHalo(const string16& text,
        const Font& font,
        const SkColor& text_color,
        const SkColor& halo_color_in,
        int x, int y, int w, int h,
        int flags)
    {
        // Some callers will have semitransparent halo colors, which we don't handle
        // (since the resulting image can have 1-bit transparency only).
        SkColor halo_color = halo_color_in | 0xFF000000;

        // Create a temporary buffer filled with the halo color. It must leave room
        // for the 1-pixel border around the text.
        CanvasSkia text_canvas(w+2, h+2, true);
        SkPaint bkgnd_paint;
        bkgnd_paint.setColor(halo_color);
        text_canvas.DrawRectInt(0, 0, w+2, h+2, bkgnd_paint);

        // Draw the text into the temporary buffer. This will have correct
        // ClearType since the background color is the same as the halo color.
        text_canvas.DrawStringInt(text, font, text_color, 1, 1, w, h, flags);

        // Windows will have cleared the alpha channel for the pixels it drew. Make it
        // opaque. We have to do this first since pixelShouldGetHalo will check for
        // 0 to see if a pixel has been modified to transparent, and black text that
        // Windows draw will look transparent to it!
        skia::MakeOpaque(&text_canvas, 0, 0, w+2, h+2);

        uint32_t halo_premul = SkPreMultiplyColor(halo_color);
        SkBitmap& text_bitmap = const_cast<SkBitmap&>(
            skia::GetTopDevice(text_canvas)->accessBitmap(true));
        for(int cur_y=0; cur_y<h+2; ++cur_y)
        {
            uint32_t* text_row = text_bitmap.getAddr32(0, cur_y);
            for(int cur_x=0; cur_x<w+2; ++cur_x)
            {
                if(text_row[cur_x] == halo_premul)
                {
                    // This pixel was not touched by the text routines. See if it borders
                    // a touched pixel in any of the 4 directions (not diagonally).
                    if(!pixelShouldGetHalo(text_bitmap, cur_x, cur_y, halo_premul))
                    {
                        text_row[cur_x] = 0; // Make transparent.
                    }
                }
                else
                {
                    text_row[cur_x] |= 0xff << SK_A32_SHIFT; // Make opaque.
                }
            }
        }

        // Draw the halo bitmap with blur.
        DrawBitmapInt(text_bitmap, x-1, y-1);
    }

    void CanvasSkia::DrawFadeTruncatingString(
        const string16& text,
        CanvasSkia::TruncateFadeMode truncate_mode,
        size_t desired_characters_to_truncate_from_head,
        const gfx::Font& font,
        const SkColor& color,
        const gfx::Rect& display_rect)
    {
        int flags = NO_ELLIPSIS;

        // If the whole string fits in the destination then just draw it directly.
        int total_string_width;
        int total_string_height;
        SizeStringInt(text, font, &total_string_width, &total_string_height,
            flags|TEXT_VALIGN_TOP);

        if(total_string_width <= display_rect.width())
        {
            DrawStringInt(text, font, color, display_rect.x(), display_rect.y(),
                display_rect.width(), display_rect.height(), 0);
            return;
        }

        int average_character_width = font.GetAverageCharacterWidth();
        int clipped_string_width = total_string_width - display_rect.width();

        // Clip the string by drawing it to the left by |offset_x|.
        int offset_x = 0;
        switch(truncate_mode)
        {
        case TruncateFadeHead:
            offset_x = clipped_string_width;
            break;
        case TruncateFadeHeadAndTail:
            DCHECK_GT(desired_characters_to_truncate_from_head, 0u);
            // Get the width of the beginning of the string we're clipping.
            string16 clipped_head_string = text.substr(0,
                desired_characters_to_truncate_from_head);
            int clipped_width;
            int clipped_height;
            SizeStringInt(clipped_head_string, font, &clipped_width,
                &clipped_height, flags);

            // This is the offset at which we start drawing. This causes the
            // beginning of the string to get clipped.
            offset_x = clipped_width;

            // Due to the fade effect the first character is hard to see.
            // We want to make sure that the first character starting at
            // |desired_characters_to_truncate_from_head| is readable so we reduce
            // the offset by a little bit.
            offset_x = std::max(0, Round(offset_x-average_character_width*2));

            // If the offset is so large that there's empty space at the tail
            // then reduce the offset so we can use up the empty space.
            offset_x = std::min(offset_x, clipped_string_width);
            break;
        }
        bool is_truncating_head = offset_x > 0;
        bool is_truncating_tail = clipped_string_width > offset_x;

        bool is_rtl = (ComputeFormatFlags(flags, text) & DT_RTLREADING) != 0;
        // |is_rtl| tells us if the given text is right to left or not. |is_rtl| can
        // be false even if the UI is set to a right to left language.
        // Now, normally, we right align all text if the UI is set to a right to
        // left language. In this case though we don't want that because we render
        // the ends of the string ourselves.
        if(!is_rtl)
        {
            flags |= TEXT_ALIGN_LEFT;
        }

        // Fade in/out about 2.5 characters of the beginning/end of the string.
        // The .5 here is helpful if one of the characters is a space.
        // Use a quarter of the display width if the string is very short.
        int gradient_width = Round(std::min(average_character_width*2.5,
            display_rect.width()/4.0));

        // Move the origin to |display_rect.origin()|. This simplifies all the
        // drawing so that both the source and destination can be (0,0).
        save(kMatrix_SaveFlag);
        TranslateInt(display_rect.x(), display_rect.y());

        gfx::Rect solid_part(gfx::Point(), display_rect.size());
        gfx::Rect head_part;
        gfx::Rect tail_part;
        if(is_truncating_head)
        {
            DivideRect(solid_part, is_rtl?PrimaryOnRight:PrimaryOnLeft,
                gradient_width, &head_part, &solid_part);
        }
        if(is_truncating_tail)
        {
            DivideRect(solid_part, is_rtl?PrimaryOnLeft:PrimaryOnRight,
                gradient_width, &tail_part, &solid_part);
        }

        // Grow |display_rect| by |offset_x|.
        gfx::Rect text_rect(gfx::Point(), display_rect.size());
        if(!is_rtl)
        {
            text_rect.set_x(text_rect.x()-offset_x);
        }
        text_rect.set_width(text_rect.width()+offset_x);

        // Create a temporary bitmap to draw the gradient to.
        scoped_ptr<SkCanvas> gradient_canvas(skia::CreateBitmapCanvas(
            display_rect.width(), display_rect.height(), false));

        {
            skia::ScopedPlatformPaint scoped_platform_paint(this);
            HDC hdc = scoped_platform_paint.GetPlatformSurface();
            if(is_truncating_head)
            {
                DrawTextGradientPart(hdc, gradient_canvas.get(), text, color,
                    font.GetNativeFont(), text_rect, head_part, is_rtl,
                    flags);
            }
            if(is_truncating_tail)
            {
                DrawTextGradientPart(hdc, gradient_canvas.get(), text, color,
                    font.GetNativeFont(), text_rect, tail_part, !is_rtl,
                    flags);
            }
        }

        // Draw the solid part.
        save(kClip_SaveFlag);
        ClipRectInt(solid_part.x(), solid_part.y(),
            solid_part.width(), solid_part.height());
        DrawStringInt(text, font, color,
            text_rect.x(), text_rect.y(),
            text_rect.width(), text_rect.height(), flags);
        restore();
        restore();
    }

} //namespace gfx